package org.dizena.cola.starter.service;

import jakarta.annotation.Resource;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.configuration.FluentConfiguration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;

import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class FlyService {
    @Value("${config.migrate}")
    private String migrates;
    @Value("${config.undo}")
    private String undoes;

    @Resource
    private DataSource dataSource;

    private static final Map<String, String> tableMap = new HashMap<>() {{
        put("auth", "flyway_auth");
        put("blog", "flyway_blog");
        put("quartz", "flyway_quartz");
    }};

    public void execute() {
        //
        if (undoes != null && undoes.length() > 0) {
            String[] projectVersions = undoes.split(",");
            for (String projectVersion : projectVersions) {
                String[] pv = projectVersion.split(">");
                String project = pv[0];
                String version = pv[1];
                if (pv.length == 3) {
                    String file = pv[2];
                }
                undo(project, version);
            }
        }
        //
        if (migrates != null && migrates.length() > 0) {
            String[] projects = migrates.split(",");
            for (String project : projects) {
                migrate(project);
            }
        }
    }

    private void undo(String project, String version) {
        String tab = tableMap.get(project);
        String path = "db/" + project + "/undo/" + version;
        List<String> locations = getAppFileResource(path);
        Collections.reverse(locations);
        for (int i = 0; i < locations.size(); i++) {
            String sqlFile = locations.get(i);
            String[] names = sqlFile.split("/");
            String script = names[names.length - 1];
            boolean undoFlag = jdbcExecSqlFile(sqlFile);
            if (undoFlag) {
                String sql = "delete from " + tab + " where script='" + script + "'";
                jdbcExecSql(sql);
            } else {
                break;
            }
        }
    }

    private void migrate(String project) {
        try {
            String tab = tableMap.get(project);
            if (tab != null) {
                String path = "db/" + project + "/migrate";
                List<String> locations = getAppDirResource(path);
                String[] locationAll = new String[locations.size()];
                locationAll = locations.toArray(locationAll);
                FluentConfiguration configuration = Flyway.configure();
                configuration.dataSource(dataSource);
                configuration.locations(locationAll);
                configuration.table(tab);
                configuration.baselineOnMigrate(true);
                configuration.placeholderReplacement(false);
                Flyway flyway = configuration.load();
                flyway.repair();
                flyway.migrate();
            }
        } catch (Exception e) {
        }
    }


    private boolean jdbcExecSql(String sql) {
        try {
            Connection conn = dataSource.getConnection();
            Statement statement = conn.createStatement();
            boolean res = statement.execute(sql);
            statement.close();
            conn.close();
            return res;
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
        return false;
    }

    private boolean jdbcExecSqlFile(String file) {
        try {
            org.springframework.core.io.Resource resource = new ClassPathResource(file);
            ResourceDatabasePopulator populator = new ResourceDatabasePopulator(resource);
            populator.execute(dataSource);
            return true;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return false;
    }

    private static List<String> getAppDirResource(String path) {
        ClassLoader classLoader = FlyService.class.getClassLoader();
        try {
            List<String> rs = new ArrayList<>();
            Enumeration<URL> resources = classLoader.getResources(path);
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                if (url.getProtocol().equals("jar")) {
                    JarURLConnection connection = (JarURLConnection) url.openConnection();
                    try (JarFile jarFile = connection.getJarFile()) {
                        Enumeration<JarEntry> entries = jarFile.entries();
                        while (entries.hasMoreElements()) {
                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();
                            if (name.startsWith(path) && entry.isDirectory() && !name.equals(path + "/")) {
                                rs.add("classpath:" + name);
                            }
                        }
                    }
                } else {
                    URI uri = url.toURI();
                    File dir = new File(uri);
                    File[] subDirs = dir.listFiles(File::isDirectory);
                    for (File subDir : subDirs) {
                        rs.add("classpath:" + path + "/" + subDir.getName());
                    }
                }
            }
            return rs;
        } catch (IOException e) {

        } catch (URISyntaxException e) {

        }
        return new ArrayList<>();
    }

    private static List<String> getAppFileResource(String path) {
        ClassLoader classLoader = FlyService.class.getClassLoader();
        try {
            List<String> rs = new ArrayList<>();
            Enumeration<URL> resources = classLoader.getResources(path);
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                if (url.getProtocol().equals("jar")) {
                    JarURLConnection connection = (JarURLConnection) url.openConnection();
                    try (JarFile jarFile = connection.getJarFile()) {
                        Enumeration<JarEntry> entries = jarFile.entries();
                        while (entries.hasMoreElements()) {
                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();
                            if (name.startsWith(path) && !entry.isDirectory()) {
                                rs.add(name);
                            }
                        }
                    }
                } else {
                    URI uri = url.toURI();
                    File dir = new File(uri);
                    File[] subDirs = dir.listFiles(File::isFile);
                    for (File subDir : subDirs) {
                        rs.add(path + "/" + subDir.getName());
                    }
                }
            }
            return rs;
        } catch (IOException e) {

        } catch (URISyntaxException e) {

        }
        return new ArrayList<>();
    }

}
